<script setup lang="ts">
import { PropType, ref, onMounted, watch, nextTick } from 'vue'
import { FormOptions, FormInstance } from './types/types.ts'
import cloneDeep from 'lodash/cloneDeep'
import {
  ElMessage,
  ElUpload,
  UploadFile,
  UploadFiles,
  UploadUserFile
} from 'element-plus'
import E from 'wangeditor'

let emits = defineEmits([
  'upload:on-preview',
  'upload:on-remove',
  'upload:on-success',
  'upload:on-error',
  'upload:on-progress',
  'upload:on-change',
  'upload:on-exceed',
  'upload:before-upload',
  'upload:before-remove'
])

let props = defineProps({
  // 表单配置项
  options: {
    type: Array as PropType<FormOptions[]>,
    required: true
  },
  // 用户自定义上传方法
  httpRequest: {
    type: Function as PropType<Function>,
    default: null
  }
})

// 表单项
let model = ref<any>(null)
// 校验规则
let rules = ref<any>(null)
// 表单实例
let form = ref<FormInstance | null>()
// 上传组件实例
let uploadRefs = ref<InstanceType<typeof ElUpload>[]>([])
// 编辑器实例
let edit = ref()

// 初始化表单的方法
let initForm = () => {
  if (props.options && props.options.length) {
    let m: any = {}
    let r: any = {}
    props.options.map((item: FormOptions) => {
      // username: '', password: ''
      m[item.prop!] = item.value
      // username:  {required: true,message: '密码不能为空',trigger: 'blue'},
      r[item.prop!] = item.rules
      // 处理editor
      if (item.type === 'editor') {
        nextTick(() => {
          if (document.getElementById('editor')) {
            let editor = new E('#editor')
            editor.config.placeholder = item.placeholder!
            editor.create()
            // 初始化富文本内容
            editor.txt.html(item.value)
            editor.config.onchange = (newHtml: string) => {
              model.value[item.prop!] = newHtml
              edit.value = editor
            }
          }
        })
      }
    })
    // 此处必须做拷贝，因为是个引用类型，如果不做拷贝，那么在表单中修改值的时候，会导致options中的值也被修改
    model.value = cloneDeep(m)
    rules.value = cloneDeep(r)
  }
}

// 重置表单
let resetFields = () => {
  // 重置element-plus表单
  form.value!.resetFields()
  // 重置自定义组件
  // 获取富文本配置项
  if (props.options && props.options.length) {
    let editorItem = props.options.find(item => item.type === 'editor')!
    edit.value.txt.html(editorItem.value)
  }
  // 重置上传组件
  uploadRefs.value.forEach(upload => {
    upload.clearFiles()
  })
}

// 表单验证方法
let validate = () => {
  return form.value!.validate
}

// 获取表单数据
let getFormData = () => {
  return model.value
}

// 分发方法
defineExpose({
  resetFields,
  validate,
  getFormData
})

onMounted(() => {
  // 初始化表单
  initForm()
})

// 监听父组件传递进来的options，如果options发生变化，那么就重新初始化表单
watch(
  () => props.options,
  () => {
    initForm()
  },
  { deep: true }
)

// 上传组件的所有方法
let onPreview = (file: UploadFile) => {
  emits('upload:on-preview', file)
}
let onRemove = (file: UploadFile, fileList: UploadFiles) => {
  emits('upload:on-remove', { file, fileList })
}
let onSuccess = (
  item: FormOptions,
  response: any,
  file: UploadFile,
  fileList: UploadFiles
) => {
  // 上传成功，给表单上传项赋值
  let uploadItem = props.options.find(formItem => formItem.prop === item.prop)!
  model.value[uploadItem.prop!] = { response, file, fileList }

  emits('upload:on-success', { response, file, fileList })
}
let onError = (err: any, file: UploadFile, fileList: UploadFiles) => {
  emits('upload:on-error', { err, file, fileList })
}
let onProgress = (event: any, file: UploadFile, fileList: UploadFiles) => {
  emits('upload:on-progress', { event, file, fileList })
}
let onChange = (file: UploadFile, fileList: UploadFiles) => {
  emits('upload:on-change', { file, fileList })
}
let onExceed = (files: File[], fileList: UploadUserFile[]) => {
  ElMessage.warning('超出文件个数限制')
  emits('upload:on-exceed', { files, fileList })
}
let beforeUpload = (item: FormOptions, file: File) => {
  let prop = item.prop
  // 判断文件格式以及文件大小配置
  if (item.uploadAttrs!.fileSize) {
    if (file.size > item.uploadAttrs!.fileSize!) {
      ElMessage.error(
        `文件大小不能超过${item.uploadAttrs!.fileSize! / 1024 / 1024}M`
      )
      return false
    }
  }
  if (item.uploadAttrs!.fileType) {
    if (item.uploadAttrs!.fileType!.indexOf(file.type) === -1) {
      ElMessage.error(
        `文件格式不正确，只能上传${item.uploadAttrs!.fileType}格式的文件`
      )
      return false
    }
  }
  emits('upload:before-upload', { prop, file })
}
let beforeRemove = (file: UploadFile, fileList: UploadFiles) => {
  emits('upload:before-remove', { file, fileList })
  return true
}
</script>

<template>
  <el-form
    ref="form"
    v-if="model"
    :validate-on-rule-change="false"
    :model="model"
    :rules="rules"
    v-bind="$attrs"
  >
    <template v-for="(item, index) in options" :key="index">
      <el-form-item
        v-if="!item.children || !item.children!.length"
        :prop="item.prop"
        :label="item.label"
      >
        <component
          v-if="item.type !== 'upload' && item.type !== 'editor'"
          v-bind="item.attrs"
          :is="`el-${item.type}`"
          v-model="model[item.prop!]"
          :placeholder="item.placeholder"
        ></component>

        <el-upload
          v-if="item.type === 'upload'"
          ref="uploadRefs"
          class="upload-demo"
          v-bind="item.uploadAttrs"
          :on-preview="onPreview"
          :on-remove="onRemove"
          :on-success="(response: any, file: UploadFile, fileList: UploadFiles) => onSuccess(item, response,file,fileList)"
          :on-error="onError"
          :on-progress="onProgress"
          :on-change="onChange"
          :on-exceed="onExceed"
          :before-upload="(file: any) => beforeUpload(item, file)"
          :before-remove="beforeRemove"
        >
          <slot name="uploadArea" />
          <template #tip>
            <slot name="uploadTip" />
          </template>
        </el-upload>
        <div v-if="item.type === 'editor'" id="editor"></div>
      </el-form-item>
      <el-form-item
        v-if="item.children && item.children.length"
        :prop="item.prop"
        :label="item.label"
      >
        <component
          v-bind="item.attrs"
          :is="`el-${item.type}`"
          v-model="model[item.prop!]"
          :placeholder="item.placeholder"
        >
          <component
            v-if="item.type === 'checkbox-group' || item.type === 'radio-group'"
            v-for="(child, i) in item.children"
            :key="i"
            :is="`el-${child.type}`"
            :label="child.value"
            :value="child.value"
          >
            {{ child.label }}
          </component>
          <component
            v-else
            v-for="(child, j) in item.children"
            :key="j"
            :is="`el-${child.type}`"
            :label="child.label"
            :value="child.value"
          ></component>
        </component>
      </el-form-item>
    </template>
    <el-form-item>
      <slot name="actions" :form="form" :model="model"></slot>
    </el-form-item>
  </el-form>
</template>

<style lang="scss" scoped></style>
